--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 4114df70d1cd02c27ca0ca7a7cd6e74370ee362f
Parents : 47afeb2
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Date : 2026-05-19T21:23:02+02:00
Added markdown rendering support, config options for enabling/disabling markdown and micron rendering, justify_msgs and space_msgs layout options.
Changes
3 files changed, 117 insertions(+), 5 deletions(-)
Diff
diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py
index 4e14b50..f3afd73 100644
--- a/nomadnet/NomadNetworkApp.py
+++ b/nomadnet/NomadNetworkApp.py
@@ -155,6 +155,10 @@ class NomadNetworkApp:
self.rrc_history_per_room_cap = 500
self.rrc_filter_loaded_history = True
self.rrc_ephemeral_notices = 600
+ self.rrc_ui_justify_msgs = False
+ self.rrc_ui_space_msgs = False
+ self.rrc_ui_render_markdown = True
+ self.rrc_ui_render_micron = True
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
@@ -925,6 +929,26 @@ class NomadNetworkApp:
except Exception: value = 0
self.rrc_ephemeral_notices = value*60
+ if option == "justify_msgs":
+ try: value = self.config["rrc"].as_bool(option)
+ except Exception: value = False
+ self.rrc_ui_justify_msgs = value
+
+ if option == "space_msgs":
+ try: value = self.config["rrc"].as_bool(option)
+ except Exception: value = False
+ self.rrc_ui_space_msgs = value
+
+ if option == "render_markdown":
+ try: value = self.config["rrc"].as_bool(option)
+ except Exception: value = True
+ self.rrc_ui_render_markdown = value
+
+ if option == "render_micron":
+ try: value = self.config["rrc"].as_bool(option)
+ except Exception: value = True
+ self.rrc_ui_render_micron = value
+
if "node" in self.config:
if not "enable_node" in self.config["node"]:
self.enable_node = False
@@ -1261,6 +1285,12 @@ filter_loaded_history = yes
# to disable and keep forever.
ephemeral_notices = 10
+# Other display and formatting options:
+render_markdown = yes
+render_micron = yes
+justify_msgs = no
+space_msgs = no
+
[node]
# Whether to enable node hosting
diff --git a/nomadnet/ui/textui/Channels.py b/nomadnet/ui/textui/Channels.py
index 00162f9..ea119d8 100644
--- a/nomadnet/ui/textui/Channels.py
+++ b/nomadnet/ui/textui/Channels.py
@@ -10,6 +10,8 @@ import nomadnet
from nomadnet.RRC import RRCHub
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox
from nomadnet.ui.textui.MicronParser import LinkableText, LinkSpec
+from RNS.Utilities.rngit.util import MarkdownToMicron
+from RNS.Utilities.rngit.highlight import SyntaxHighlighter
from .MicronParser import markup_to_attrmaps
from nomadnet.util import strip_modifiers
@@ -897,6 +899,49 @@ class _ChatLinkDelegate:
except Exception as e:
RNS.log("Could not open page link: "+str(e), RNS.LOG_ERROR)
+def strip_micron(text):
+ text = re.sub(r'`[FB][0-9a-fA-F]{3}', '', text)
+ text = re.sub(r'`[FB]T[0-9a-fA-F]{6}', '', text)
+ text = re.sub(r'`[!*_=]', '', text)
+ text = re.sub(r'`f`b', '', text)
+ text = re.sub(r'`f', '', text)
+ text = re.sub(r'`b', '', text)
+ text = re.sub(r'`<', '', text)
+ text = re.sub(r'`>', '', text)
+ text = re.sub(r'`{', '', text)
+ return text
+
+def strip_escaped_micron(text):
+ text = re.sub(r'¦[FB][0-9a-fA-F]{3}', '', text)
+ text = re.sub(r'¦[FB]T[0-9a-fA-F]{6}', '', text)
+ text = re.sub(r'¦[!*_=]', '', text)
+ text = re.sub(r'¦f`b', '', text)
+ text = re.sub(r'¦f', '', text)
+ text = re.sub(r'¦b', '', text)
+ text = re.sub(r'¦<', '', text)
+ text = re.sub(r'¦>', '', text)
+ text = re.sub(r'¦{', '', text)
+ return text
+
+def unescape_micron(text):
+ text = re.sub(r'¦([FB][0-9a-fA-F]{3})', r'`\1', text)
+ text = re.sub(r'¦([FB]T[0-9a-fA-F]{6})', r'`\1', text)
+ text = re.sub(r'¦([!*_=])', r'`\1', text)
+ text = re.sub(r'¦(f`b)', r'`\1', text)
+ text = re.sub(r'¦(f)', r'`\1', text)
+ text = re.sub(r'¦(b)', r'`\1', text)
+ text = re.sub(r'¦(<)', r'`\1', text)
+ text = re.sub(r'¦(>)', r'`\1', text)
+ text = re.sub(r'¦({)', r'`\1', text)
+ return text
+
+def strip_non_formatting_tags(text):
+ text = re.sub(r'`<', '', text)
+ text = re.sub(r'`>', '', text)
+ text = re.sub(r'`{', '', text)
+ return text
+
+mdc = MarkdownToMicron(max_width=80, syntax_highlighter=SyntaxHighlighter(), url_scope=None)
def _message_widget(app, hub, m, link_delegate=None):
t = theme_dark if app.config["textui"]["theme"] == nomadnet.ui.TextUI.THEME_DARK else theme_light
g = app.ui.glyphs
@@ -960,16 +1005,34 @@ def _message_widget(app, hub, m, link_delegate=None):
label = mb.split("`")[0]
url = f"{kind}://{mb}"
link_mu = f"`_`F{t['link']}`[{label}`{url}]`f`_"
+ link_md = f"[{label}]({url})"
message_body += link_mu
- else: message_body += mb
+ else:
+ if app.rrc_ui_render_micron:
+ mbo = mdc.format_block(mb) if app.rrc_ui_render_markdown else mb
+ mbo = unescape_micron(mbo)
+ message_body += strip_non_formatting_tags(mbo)
+
+ else:
+ mbo = mdc.format_block(strip_escaped_micron(mb)) if app.rrc_ui_render_markdown else strip_escaped_micron(mb)
+ message_body += strip_non_formatting_tags(mbo)
nick_attr = f"`F{t['nick_self']}" if own else f"`F{t['nick_peer']}"
irc_ts = f"`F{t['ts']}"
- prefix_micron = f"{irc_ts}{_ts_prefix_raw(m.ts)}`f {nick_attr}<{sender}>`f "
-
- rendered = _render_body(f"{prefix_micron}{message_body}", link_delegate=ld)
- final_widget = urwid.Padding(urwid.Pile(rendered), left=1)
+ prefix_micron = f"{irc_ts}{_ts_prefix_raw(m.ts)}"
+ nick_micron = f"`f{nick_attr}<{sender}>`f "
+ if app.rrc_ui_justify_msgs:
+ prefix_rendered = _render_body(f"{prefix_micron}")
+ body_rendered = _render_body(f"{nick_micron}{message_body}")
+ if app.rrc_ui_space_msgs: body_rendered.append(urwid.Text(""))
+ columns = urwid.Columns([(urwid.PACK, urwid.Pile(prefix_rendered)), urwid.Pile(body_rendered)], dividechars=1)
+ final_widget = urwid.Padding(columns, left=1)
+
+ else:
+ rendered = _render_body(f"{prefix_micron}{nick_micron}{message_body}", link_delegate=ld)
+ if app.rrc_ui_space_msgs: rendered.append(urwid.Text(""))
+ final_widget = urwid.Padding(urwid.Pile(rendered), left=1)
final_widget.msg = m
return final_widget
diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py
index 66def19..840dcc8 100644
--- a/nomadnet/ui/textui/Guide.py
+++ b/nomadnet/ui/textui/Guide.py
@@ -793,6 +793,25 @@ Whether to filter notices and system message when initially loading room history
If enabled, notices and system messages will disappear from the history after a while. 0 disables, otherwise sets the timeout in minutes.
<
+>>>
+`!render_markdown = yes`!
+>>>>
+Whether or not to render markdown formatting in messages.
+<
+
+>>>
+`!render_micron = yes`!
+>>>>
+Whether or not to render micron formatting in messages. When using micron in messages, use the ¦ character in place of backticks.
+<
+
+>>>
+`!justify_msgs = no`!
+`!space_msgs = no`!
+>>>>
+Rendering layout options for messages.
+<
+
>> Text UI Section
This section hold configuration directives related to the look and feel of the text-based user interface of the program. It is delimited by the `![textui]`! header in the configuration file. Available directives, along with their default values, are as follows:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────